package ngat.oss;

import ngat.message.OSS.*;
import ngat.astrometry.*;
import ngat.util.*;
import ngat.util.logging.*;
import ngat.phase2.*;
import ngat.instrument.*;
import ngat.phase2.nonpersist.*;

import java.util.*;
import java.io.*;
import java.text.*;

import jyd.dbms.*;
import jyd.storable.*;
import jyd.collection.*;

/** 
 * Client: RCS.
 * Action: Implements the OSS scheduling algorithm as defined in distributed form by
 * the PhaseII database objects using parameters specified in the static Scheduling object.
 * If the delay between NOW and the start of the Next FixedGroup (if no MoveableGroups can
 * be Scheduled NOW) is longer than a predefined period an OSS_Error is returned by the exec
 * method, otherwise a ref to the Moveable/Fixed Group to observe next. The Ephemeris and
 * Monitoring Groups will complicate this somewhat. Note that their veto functions should
 * eliminate them outside of their 'sloppiness' limits. 
 * TBD: add stuff to deal with Ephem / Monitor groups .. 
 */
public class SCHEDULEImpl extends TransactionImpl implements Serializable {
  
    /** Special key used by the RCS to lock the database Group records.*/
    protected static int RCS_KEY = 0xABABABAB;
    
    /** Specifies the longest period the caller (RCS) wants to remain idle 
     * if no Moveable Groups can be scheduled at this time. It is the longest
     * period between NOW and the start of the next Fixed (or Sliding) Group
     * that can be returned for scheduling. If no Groups can satisfy the 
     * constraint, the RCS may carry out Calibration/Focus etc before asking
     * for another attempt at scheduling. */   
    protected long maxIdle;
    
    /** Specifies the latest time by which the returned observation sequence must
     * be expected to have been completed by.*/
    protected long latestTime;

    /** Specifies the time difference to add to the current time (now) to give
     * an initial float period for the selected group's start time - i.e. the 
     * selected Group must be able to start any time between <i>now</i> and
     * <i>now+deltaTime</i>.*/
    protected long deltaTime;

    /** The ID of the scheduler to be used if none is specified then the
     * default scheduler is employed - this is currently not defined.*/
    protected String schedId;
    
    /** Determines whether the returned Group should be locked after fetching - 
     * This is normally true for robotic operations - but can be set false to
     * allow simulations/statistic to be performed on data while leaving the 
     * records accessable to users/admin etc - i.e. selection with replacement.*/
    protected boolean lock;

    /** The editor name to use to set the lock.*/
    protected String lockOwner;

    /** A key used to lock the Group's database entry - to prevent any modifications
     * while it is being scheduled.*/
    protected int key;
    
    /** Create a SCHEDULEImpl to handle the specified SCHEDULE request. */
    public SCHEDULEImpl(SCHEDULE request) {
	super(request);	
	this.maxIdle     = ((SCHEDULE)request).getMaxIdle();
	this.latestTime  = ((SCHEDULE)request).getLatestTime();
	this.lock        = ((SCHEDULE)request).getLock();
	this.deltaTime   = ((SCHEDULE)request).getDeltaTime();
	this.schedId     = ((SCHEDULE)request).getSchedId();
	this.lockOwner   = ((SCHEDULE)request).getLockOwner();
	key = RCS_KEY;
    }
    

    /** Calls Scheduler to pick the best/next Group of Observations.
     * @return Details of the best <i>moveable</i> or <i>fixed</i> Group to execute next.
     * or an OSS_Error if none available within the specified period.
     */
    public TRANSACTION_DONE exec() {

	myLog = LogManager.getLogger("SCHEDULE");
	myLog.log(1, CLASS, id, "exec", 
		   "Start Schedule run for lock owner: "+lockOwner);
	
	if (lockOwner == null)
	    lockOwner = "RCS";
	
	Transaction t  = null;
	Root  root     = null;
	
	try {
	    
	    t = Transaction.current();
	    
	    root = OSS_Utilities.fetchRoot();
	    
	    // Check it exists.
	    if (root == null) {		
		myLog.log(1, CLASS, id, "exec", 
			   "Root: Not in database.");
		return error(TRANSACTION.PATH_RESOLUTION_ERROR, 
				     "Could not find Root: ");
	    } 
	
	    myLog.log(1, CLASS, id, "exec","Found database root: "+root.getName());
	    	    	 	    
	    // Set the current time - not using Sys.cTM() for now.
	    Scheduling.setNow(System.currentTimeMillis());
	    
	    // Cache Moon's position.
	    Position moon = Astrometry.getLunarPosition();	   
	    Scheduling.setLunarPosition(moon);
	    myLog.log(1, CLASS, id, "exec","Cached Lunar position:"+
		       "\n\tRA:   "+Position.toHMSString(moon.getRA())+
		       "\n\tDec:  "+Position.toDMSString(moon.getDec())+
		       "\n\tAzimuth: "+Position.toDegrees(moon.getAzimuth(), 3)+
		       "\n\tAltitude: "+Position.toDegrees(moon.getAltitude(), 3));
	    
	    // Cache Sun's position.
	    Position sun = Astrometry.getSolarPosition();	   
	    Scheduling.setSolarPosition(sun);
	    myLog.log(1, CLASS, id, "exec","Cached Solar position:"+
		       "\n\tRA:   "+Position.toHMSString(sun.getRA())+
		       "\n\tDec:  "+Position.toDMSString(sun.getDec())+
		       "\n\tAzimuth: "+Position.toDegrees(sun.getAzimuth(), 3)+
		       "\n\tAltitude: "+Position.toDegrees(sun.getAltitude(), 3));
	    
	    // Cache next fixed Group's position, time etc.
	    //FixedGroup nfg = root.getNextFixedGroup();
	    
	    // NEW FORM using static from Scheduling - but NOT in SIM mode.
	    
	    // Cache the RCS-imposed time constraint.
	    Scheduling.setNextTimeConstraint(latestTime);
	    myLog.log(1, CLASS, id, "exec","Set caller's latest completion time constraint: "+sdf.format(new Date(latestTime)));
	    
	    myLog.log(1, CLASS, id, "exec","Selected schedule mode: "+schedId);
	    
	    // Set the initial start time float period.
	    //Scheduling.setDeltaTime(deltaTime);
	    Scheduling.setDeltaTime(10000L);

	    myLog.log(1, CLASS, id, "exec","Set Delta time to: "+(deltaTime/1000.0)+"secs."+
		       "\nGroups may start between now: "+sdf.format(new Date())+
		       " and: "+sdf.format(new Date(System.currentTimeMillis()+deltaTime)));
	    
	    // -----------------------------
	    // This is where it all happens.
	    // -----------------------------
	  
	    // NEW FORM USING EXTERNAL SCHEDULER.
	    myLog.log(1, CLASS, id, "exec","Looking for Scheduler....");
	    Scheduler scheduler = Scheduling.getScheduler();
	    
	    // No Scheduler.
	    if (scheduler == null) { 
		myLog.log(1, CLASS, id, "exec",
			   "No scheduler could be found");
		return error(SCHEDULE.SCHEDULER_NOT_AVAILABLE,
			     "No scheduler could be found");
	    }
	    
	    // Setup any initialization before call.
	    ConfigurationProperties params = new ConfigurationProperties();
	    params.setProperty("lock.owner", lockOwner);
	    params.setProperty("max.idle",   ""+maxIdle);
	    
	    scheduler.init(params);

	    myLog.log(1, CLASS, id, "exec","Located and initialized Scheduler: "+scheduler.getClass().getName());


	    long start = System.currentTimeMillis();
	    // how do we pass the lockOwner in ? to Scheduling or via this call
	    // or scheduler.clearLocks(lockOwner) - means new method in interface.
	    ScheduleDescriptor schedDesc = scheduler.getBestGroup();
	    long stop  = System.currentTimeMillis();
	   
	    myLog.log(1, CLASS, id, "exec","Scheduling completed in "+((stop-start)/1000.0)+" secs."+
		       schedDesc.toString());
	    	  
	    if (schedDesc == null) {
		return error(SCHEDULE.NO_SCHEDULABLE_GROUPS,
			     "Unable to find any schedulable or Fixed Group in OSS Database:");
	    }

	    Group  bestGroup = schedDesc.getGroup();
	    double score     = schedDesc.getScore();
	    long   execTime  = schedDesc.getExecTime();	    
	 
	    if (bestGroup != null) {
		myLog.log(1, CLASS, id, "exec","Looking for nominated optimal Group in OSS Database:");
		
		// A moveable Group has been found - fetch it.
		Proposal proposal = OSS_Utilities.fetchProposal(new Path(bestGroup.getFullPath()));
		    
		// Fetch the Proposal for the Group.
		if (proposal == null) {
		    myLog.log(1, CLASS, id, "exec", 
			       "Proposal: "+bestGroup.getFullPath()+" Not in database.");
		    return error(TRANSACTION.PATH_RESOLUTION_ERROR, 
				 "Could not find Proposal: "+bestGroup.getFullPath());
		} 
		
		// Ok we've found its Proposal, get the Group back again.
		bestGroup = proposal.findGroup(bestGroup.getName());
		    
		// Check its there - why wouldn't it be?
		if (bestGroup == null) {
		    myLog.log(1, CLASS, id, "exec", 
			       "Group: "+bestGroup.getName()+" Not in database.");
		    return error(TRANSACTION.PATH_RESOLUTION_ERROR, 
				 "Could not find Group: "+bestGroup.getName());
		} 
		
		myLog.log(1, CLASS, id, "exec","Fetched optimal group "+bestGroup.getFullPath());
		    
		// OK lock it if required.
		if (lock) {
		    bestGroup.lock(key);		  
		    myLog.log(1, CLASS, id, "exec","Locked optimal group with key: "+key);
		    proposal.lock(key);
		    proposal.setLastLocked(System.currentTimeMillis());
		    proposal.setLastEditor(lockOwner);
		    proposal.setAdmin(true);
		    myLog.log(1, CLASS, id, "exec","TEMP:(Admin) Locked owner proposal with key: "+key);		   
		}
		
		// Save the Proposal.
		try {
		    OSS_Utilities.storeProposal(proposal);
		} catch (IOException ix) {
		    myLog.log(1, CLASS, id, "exec",
			       "Proposal ["+proposal.getFullPath()+
			       "] Unable to Store: "+ix);
		    return error(TRANSACTION.DATABASE_UPDATE_ERROR, 
				 "Unable to store proposal",
				 ix);
		}
		myLog.log(1, CLASS, id, "exec","Stored Proposal: "+proposal.getName());
		myLog.log(1, CLASS, id, "exec","Returning schedule description:");
		
		Scheduling.setCurrentGroup(bestGroup);
		Scheduling.setCurrentGroupStartTime(System.currentTimeMillis());

		SCHEDULE_DONE done = new SCHEDULE_DONE(id);
		done.setGroup(bestGroup);	
		done.setScore(schedDesc.getScore());
		done.setLatestTime(latestTime);
		done.setNominalTime((long)bestGroup.getNominalExecutionTime());
		done.setSuccessful(true);
		return done;
		    
	    } else {

		return error(SCHEDULE.NO_SCHEDULABLE_GROUPS,
			     "Unable to find any schedulable or Fixed Group in OSS Database:");
		
	    }
	    
	} catch (Exception e) {
	    myLog.log(1, CLASS, id, "exec",  "Error during transaction: " + e);
	    e.printStackTrace();
	    return error(TRANSACTION.DATABASE_READ_ERROR, 
			 "Error reading OSS Database: "+ e);
	} finally {
	    t.commit();
	    myLog.log(1, CLASS, id, "exec", logBuffer.toString());
	    
	}

    } // (exec).
    
} // class def [SCHEDULE_Impl].






 




